package com.functest.jpwise

import com.functest.jpwise.core.ParameterValue
import com.functest.orm.ConditionOp

import static com.functest.util.AnnotatedClosure.annotate

/**
 * @author: DavydovMD
 * Date: 18.06.13
 * Time: 11:11
 */


interface ParamValueMatcher {
    boolean matches(ParameterValue value)
}


class ClosureBasedMatcher implements ParamValueMatcher {
    Closure<Boolean> matcherClosure;

    static ClosureBasedMatcher valueWith(Closure<Boolean> c) {
        new ClosureBasedMatcher(c)
    }

    ClosureBasedMatcher(Closure<Boolean> matcherClosure) {
        this.matcherClosure = matcherClosure.clone() as Closure<Boolean>
    }

    ClosureBasedMatcher and(Closure<Boolean> addClosureParam) {
        Closure<Boolean> oldMatcherClosure = matcherClosure.clone() as Closure<Boolean>
        Closure<Boolean> addClosure = addClosureParam.clone() as Closure<Boolean>
        matcherClosure =  {ParameterValue v -> oldMatcherClosure(v) && addClosure(v)}
        this
    }

    @Override
    boolean matches(ParameterValue value) {
        matcherClosure(value)
    }
}

class ConditionBasedMatcher implements ParamValueMatcher {
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("ParamValueMatcher{");
        sb.append("conditions=").append(conditions);
        sb.append('}');
        return sb.toString();
    }

    enum Field {
        Value(annotate('Value') { ParameterValue v -> v.get() }), Name(annotate('Name') { ParameterValue v -> v.name }), ParameterName(annotate('ParameterName') { ParameterValue v -> v.parentParameter.name })
        Closure getterClosure;

        Field(Closure getterClosure) {
            this.getterClosure = getterClosure;

        }
        /**
         * Проверяем , соответствует одно из полей объекта ParameterValue заданному условию
         * В зависимости от типа @locatorValue, сравнивает поле объекта либо по значению, либо по регулярке (если locatorValue - Pattern), либо с помощью Closure
         * @param value - объект типа ParameterValue, который мы проверяем на соответствие условию
         * @param locatorValue - значение, с которым сравниваем то или иное поле value
         * @return соответствуюет/не соответствует
         */


        boolean matches(ParameterValue value, ConditionOp op, Object locatorValue) {
            op.apply(getterClosure(value), locatorValue)
        }
    }

    static class AtomicCondition {
        Field type
        ConditionOp op
        Object value

        boolean matches(ParameterValue paramValue) {
            assert paramValue
            type.matches(paramValue, op, value)
        }
    }

    List<AtomicCondition> conditions = [];

    ConditionBasedMatcher(Field field, ConditionOp op, Object value) {
        conditions = [new AtomicCondition(type: field, op: op, value: value)]
    }


    ConditionBasedMatcher(Field field1, ConditionOp op1, Object value1, Field field2, ConditionOp op2, Object value2) {
        conditions = [new AtomicCondition(type: field1, op: op1, value: value1), new AtomicCondition(type: field2, op: op2, value: value2)]
    }


    ConditionBasedMatcher(List<AtomicCondition> conditions) {
        this.conditions = conditions
    }

    boolean matches(ParameterValue value) {
        for (AtomicCondition condition : conditions) {
            if (!condition.matches(value)) return false
        }
        true;
    }
}

// All but


class RestrictionsBuilder {
    String parameterName
    private List<Closure<Boolean>> incompatibilityClosures = [] // Closure returns true if incompatible
    private ParamValueMatcher thisParamMatcher

    RestrictionsBuilder(String name) {
        this.parameterName = name
        thisParamMatcher = new ConditionBasedMatcher(ConditionBasedMatcher.Field.ParameterName, ConditionOp.EQ, name)
    }
//
//    RestrictionsBuilder incompatibleWith(ParamValueMatcher otherParameterMatcher) {
//        incompatibilityClosures << annotate("$parameterName parameter not compatible with $otherParameterMatcher") {
//            ParameterValue thisValue, ParameterValue thatValue ->
//                (thisParamMatcher.matches(thisValue) && otherParameterMatcher.matches(thatValue))
//        }
//        this
//    }

    RestrictionsBuilder restrict(ParamValueMatcher thisValueMatcher, ParamValueMatcher thatValueMatcher) {
        incompatibilityClosures << annotate("$thisValueMatcher not compatible with $thatValueMatcher") {
            ParameterValue thisValue, ParameterValue thatValue ->
                (thisValueMatcher.matches(thisValue) && thatValueMatcher.matches(thatValue))
        }
        this
    }

    List<Closure<Boolean>> build() {
        [
                {
                    ParameterValue thisValue, ParameterValue thatValue ->
                        boolean isIncompatible = false;
                        for (closure in incompatibilityClosures) {
                            if (closure(thisValue, thatValue)) {
                                isIncompatible = true
                                break
                            }
                        }
                        !isIncompatible
                }
        ]
    }


}


class CompatibleIf {
    /**
     * Parameter value to be used when all other values are inapplicatble
     * @param locator
     * @return
     */
    static Closure<Boolean> thisIs(ParamValueMatcher leftMatcher) {
        annotate "thisIs $leftMatcher", valuesAre(leftMatcher, null)
    }

    static Closure<Boolean> thatIs(ParamValueMatcher rightMatcher) {
        annotate "thatIs $rightMatcher", valuesAre(null, rightMatcher)
    }

    static Closure<Boolean> any() {
        annotate('any') { ParameterValue left, ParameterValue right -> true } as Closure<Boolean>
    }

    static Closure<Boolean> valuesAre(ParamValueMatcher thisMatcher, ParamValueMatcher thatMatcher) {
        annotate("valuesAre $thisMatcher, $thatMatcher") { ParameterValue left, ParameterValue right ->
            boolean res = true
            if (thisMatcher) {
                res &= thisMatcher.matches(left)
            }
            if (res && thatMatcher) {
                res &= thatMatcher.matches(right)
            }
            res
        }
    }
}
